﻿using DotNetty.Transport.Bootstrapping;
using DotNetty.Transport.Channels;
using DotNetty.Transport.Channels.Sockets;
using System;
using System.Net;
using System.Threading.Tasks;

namespace DotNetty.Wraper
{
    class UdpSocketBuilder : IUdpSocketBuilder
    {
        public UdpSocketBuilder(ClientOption _option)
        {
            option = _option;
        }
        public ClientOption option { get; }
        UdpSocketEvent _socketEvent { get; } = new UdpSocketEvent();

        public IUdpSocketBuilder OnChannelRegistered(Action<IUdpSocket, Transport.Channels.IChannel> action)
        {
            _socketEvent.OnChannelRegistered = action;

            return this;
        }

        public IUdpSocketBuilder OnChannelUnregistered(Action<IUdpSocket, Transport.Channels.IChannel> action)
        {
            _socketEvent.OnChannelUnregistered = action;

            return this;
        }
        public IUdpSocketBuilder OnClose(Action<IUdpSocket> action)
        {
            _socketEvent.OnClose = action;

            return this;
        }

        public IUdpSocketBuilder OnException(Action<Exception> action)
        {
            _socketEvent.OnException = action;

            return this;
        }

        public IUdpSocketBuilder OnRecieve(Action<IUdpSocket, EndPoint, byte[]> action)
        {
            _socketEvent.OnRecieve = action;

            return this;
        }

        public IUdpSocketBuilder OnSend(Action<IUdpSocket, EndPoint, byte[]> action)
        {
            _socketEvent.OnSend = action;

            return this;
        }

        public IUdpSocketBuilder OnStarted(Action<IUdpSocket> action)
        {
            _socketEvent.OnStarted = action;

            return this;
        }

        public async Task<IUdpSocket> BuildAsync(Action<Transport.Channels.IChannelPipeline> OnPipelineAction = null, Func<IUdpSocket, BaseSimpleChannelInboundHandler<object>> addChannelHandler = null)
        {
            if (option == null) throw new ArgumentNullException("option");
            //Environment.SetEnvironmentVariable("io.netty.allocator.numDirectArenas", "0");
            //Environment.SetEnvironmentVariable("io.netty.allocator.numHeapArenas", "0");
            var udpClient = new UdpSocket(option.Port, _socketEvent);

            IEventLoopGroup group;
            if (option.UseLibuv)
            {
                group = new Transport.Libuv.EventLoopGroup();
            }
            else
            {
                group = new MultithreadEventLoopGroup();
            }
            var _bootstrap = new Bootstrap();
            if (option.TcpNodelay)
            {
                _bootstrap.Option(ChannelOption.TcpNodelay, true);
            }
            if (option.SoBroadcast)
            {
                _bootstrap.Option(ChannelOption.SoBroadcast, true);
            }
            if (option.ConnectTimeout > 0)
            {
                _bootstrap.Option(ChannelOption.ConnectTimeout, TimeSpan.FromMilliseconds(option.ConnectTimeout));
            }
            var clientChannel = await _bootstrap//new Bootstrap()
                .Group(group)
                .Channel<SocketDatagramChannel>()
                .Handler(new ActionChannelInitializer<IChannel>(channel =>
                {
                    IChannelPipeline pipeline = channel.Pipeline;
                    //if (tlsCertificate != null)
                    //{
                    //    pipeline.AddLast(Handlers.Tls.TlsHandler.Server(tlsCertificate));
                    //}
                    OnPipelineAction?.Invoke(pipeline);

                    if (addChannelHandler != null)
                        pipeline.AddLast(addChannelHandler(udpClient));
                    else
                        pipeline.AddLast(new CommonChannelHandler(udpClient));

                })).BindAsync(option.Port);

            udpClient.SetChannel(clientChannel);
            udpClient.AfterClose = () =>
            {
                group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(option.QuietPeriod), TimeSpan.FromSeconds(option.ShutdownTimeout)).Wait();
            };
            return await Task.FromResult(udpClient);
        }
    }
}
